/* -*-  Mode:C; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
/*
 * Copyright (c) 1999, 2000 Massachusetts Institute of Technology.
 *
 * This software is being provided by the copyright holders under the GNU
 * General Public License, either version 2 or, at your discretion, any later
 * version. (See the file `LICENSE' in the source distribution.)
 *
 * This software is provided "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The entire risk as
 * to the quality and performance of the program is with you. See the GNU
 * General Public License for more details.
 *
 * The name and trademarks of copyright holders may NOT be used in 
 * advertising or publicity pertaining to the software without specific, 
 * written prior permission. Title to copyright in this software and any 
 * associated documentation will at all times remain with copyright 
 * holders. See the file CMAUTHORS which should have accompanied this software 
 * for a list of all copyright holders. 
 * 
 * Particular files in this distribution may be derived from previously
 * copyrighted software. This copyright applies only to those changes made by
 * the copyright holders listed in the CMAUTHORS file.
 */
/*
 * An implementation of the Congestion Manager (CM) for Linux.
 * This file implements a rather generic CM congestion controller.
 * The TCP-style congestion controller is in cm_tcpcongctl.c
 */
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <net/cm.h>
#include <linux/wanpipe.h>

/*
 * Initialize congestion controller state when a new CM is instantiated 
 * for this destination (macroflow).  Called by cm_open().
 */
cm_congctl_t *cmcc_init(cm_macroflow_t *mflow)
{
	cm_congctl_t *cmcc;

	/* printk("in cmcc_init for mflow %p\n", mflow); */
	if ((cmcc=(cm_congctl_t *)kmalloc(sizeof(cm_congctl_t), GFP_KERNEL)) 
	    == NULL)
		return NULL;
	/* printk("leaving cmcc_init for mflow %p\n", mflow); */
	cmcc->mflow = mflow;
	cmcc->active = 0;
	cmcc->ccstate_lock = SPIN_LOCK_UNLOCKED;
	return cmcc;
}

void cmcc_free(cm_macroflow_t *mf)
{
	kfree(mf->congctl);
}

void cmcc_update(cm_congctl_t *cmcc, int nsent, int nrecd, 
		 u32 rtt, int lossmode)
{
	spin_lock_bh(&(cmcc->ccstate_lock));
	cmcc->ops.update(cmcc, nsent, nrecd, rtt, lossmode);
	spin_unlock_bh(&(cmcc->ccstate_lock));
	/* schedule different flow on mflow*/
	cmcc_wakeup(cmcc);
}

void cmcc_query(cm_congctl_t *cmcc, double *bw, double *srtt, 
		       double *rttvar)
{
	spin_lock_bh(&(cmcc->ccstate_lock));
	cmcc->ops.query(cmcc, bw, srtt, rttvar);
	spin_unlock_bh(&(cmcc->ccstate_lock));
}

void cmcc_notify(cm_congctl_t *cmcc, int nsent)
{
	__u32 rto = 0;
	__u32 expires = (__u32)jiffies;
	cm_macroflow_t *mflow = cmcc->mflow;
	rto = cmcc->state.rto;
	if (rto == 0) rto = CM_MAX_RTO;
	expires += rto;
	spin_lock_bh(&(cmcc->ccstate_lock));
	cmcc->ops.notify(cmcc, nsent);
        if (!mflow->cm_restart_timer.prev) {
                mflow->cm_restart_timer.expires = expires;
                add_timer(&mflow->cm_restart_timer);
        } else {
                if (time_before(mflow->cm_restart_timer.expires,expires))
			mod_timer(&mflow->cm_restart_timer,expires);
	}	
	spin_unlock_bh(&(cmcc->ccstate_lock));
}

void cmcc_wakeup(cm_congctl_t *cmcc) 
{
	if (cmcc->active)
		/* don't really want a bunch to queue up waiting 
		   it's ok that there may be a race condition here because
		   is is ok that a few calls wait in line 
		   could remove this and things would still work...
		   */
		return;
	spin_lock_bh(&(cmcc->ccstate_lock));
	if (cmcc->active) {
		/* why do this?.... wakeup must give up the lock when it 
		 passes control to the scheduler (to avoid deadlock).
		 However, we don't want anyone to sneak in here in the
		 meantime */
		spin_unlock_bh(&(cmcc->ccstate_lock));
		return;
	}
	cmcc->active = 1;
	cmcc->ops.wakeup(cmcc);
	cmcc->active = 0;
	spin_unlock_bh(&(cmcc->ccstate_lock));
}

void cmcc_restart(cm_congctl_t *cmcc)
{
	__u32 now = (__u32)(jiffies);
	if (cmcc->congctl_type == 0)
		cmtcpcc_restart(cmcc,now);
#ifdef CONFIG_CM_BINOMIAL
	if (cmcc->congctl_type == 1)
		cmbincc_restart(cmcc,now);
#endif
}

